package pers.richard.ormybatis.aop;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.TypeUtil;
import pers.richard.ormybatis.annotation.SelectId;
import pers.richard.ormybatis.annotation.SelectParamName;
import pers.richard.ormybatis.dao.OrmybatisBaseDao;
import pers.richard.ormybatis.util.ReflectMethodByArgNameUtil;

/**
 * 通过Dao方法名和传参名,自动拼成select语句执行. 
 */
@Aspect
public class OrmybatisMatchAop {
	static final Logger log = LoggerFactory.getLogger(OrmybatisMatchAop.class);
	
	@Around("@within(pers.richard.ormybatis.annotation.DaoConfig) && ( @annotation(pers.richard.ormybatis.annotation.SelectId) || @annotation(pers.richard.ormybatis.annotation.SelectParamName) ) ")
    public Object around(ProceedingJoinPoint point) throws Throwable {
		Object[] argsValues = point.getArgs();
		Method method = ((MethodSignature) point.getSignature()).getMethod();
		Map<String, Object> paramterMap = this.getParamterMap(method, argsValues);
		OrmybatisBaseDao<?> dao = (OrmybatisBaseDao)point.getTarget();
		//按方法的参数名和值执行
		SelectParamName selectParamAnnotation = AnnotationUtils.findAnnotation(method, SelectParamName.class);
		if (selectParamAnnotation != null) {
			List resultList = dao.selectList(paramterMap);
			return this.converToMethodReturnClass(method, resultList);
		}
		//按方法名调用mapper的中的sql
		SelectId selectIdAnnotation = AnnotationUtils.findAnnotation(method, SelectId.class);
		if (selectIdAnnotation != null) {
			Object paramObject = (paramterMap.size() == 1) ? paramterMap.values().iterator().next() : paramterMap;
			List resultList = dao.selectListStatement(method.getName(), paramObject);
			return this.converToMethodReturnClass(method, resultList);
		}
		return null; 
    }

	/**
	 * 按方法参数名和传参值,拼成Map,用于select语句入参
	 */
	private Map<String, Object> getParamterMap(Method method, Object[] argsValues){
		String[] argNameArray = ReflectMethodByArgNameUtil.getArgNames(method);
		Map<String, Object> parameter = this.getSqlParameter(argsValues, argNameArray);
		Map<String, Object> paraMap = new LinkedHashMap<>();
		for (Map.Entry<String, Object> entry : parameter.entrySet()) {
			Object value = entry.getValue();
			if (ObjectUtil.isEmpty(value)) {
				continue;
			}
			Class valueClz = value.getClass();
			if (ReflectMethodByArgNameUtil.isPrimitive(valueClz)) {
				paraMap.put(entry.getKey(), value);
			} else if (BeanUtil.isBean(valueClz)) {
				Map<String, Object> valueMap = BeanUtil.beanToMap(value);
				paraMap.putAll(valueMap);
			} else if (Map.class.isAssignableFrom(valueClz)) {
				Map mapValue = (Map)value;
				for (Object key : mapValue.keySet()) {
					String k = Convert.toStr(key);
					paraMap.put(k, mapValue.get(key));
				}
			}
		}
		return paraMap;
	}
	
	/**
	 * 	拼装查询参数
	 * @param argValueArray 参数值
	 * @param argNameArray 参数名
	 * @return
	 */
	private Map<String, Object> getSqlParameter(Object[] argValueArray, String[] argNameArray)  {
		Map<String, Object> param = new LinkedHashMap<>();
		for (int i = 0; i < argNameArray.length; i++) {
			String argName = argNameArray[i];
			Object argValue = argValueArray[i];
			param.put(argName, argValue);
		}
		return param;
	}
	
	/**
	 * 转换成方法返回值的类型
	 */
	private Object converToMethodReturnClass(Method method, List<?> resultList) {
		Class<?> returnClass = method.getReturnType();
		//得到返回类型的泛型
		Type[] genericTypes = TypeUtil.getTypeArguments(method.getGenericReturnType());
		if (Collection.class.isAssignableFrom(returnClass)) {
			Type elementType = ArrayUtil.get(genericTypes, 0);
			return Convert.toCollection(returnClass, (Class)elementType, resultList);
		}
		Object resultObject = CollUtil.get(resultList, 0);
		if (Map.class.isAssignableFrom(returnClass)) {
			Map<String, Object> beanToMap = BeanUtil.beanToMap(resultObject);
			if (ArrayUtil.isEmpty(genericTypes)) {
				return beanToMap;
			}
			Class<?> keyClz = (Class)genericTypes[0];
			Class<?> valueClz =  (Class)genericTypes[1];
			Map resultMap = new LinkedHashMap();
			for (Map.Entry<String, Object> entry : beanToMap.entrySet()) {
				Object newKey = Convert.convert(keyClz, entry.getKey());
				Object newValue = Convert.convert(valueClz, entry.getValue());
				resultMap.put(newKey, newValue);
			}
			return resultMap;
		}
		return Convert.convert(returnClass, resultObject);
	}
	
}